
import static java.net.HttpURLConnection.HTTP_OK;

import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;


/**
 * Handle the complicated network bit for the ConcurrencyHandler.
 *
 * This allows the ConcurrencyHandler to be network-type agnostic and remains 
 * agnostic to any of the higher level protocol details
 */
public final class ConcurrencyServer implements HttpHandler
{
    /* Our URL */
    private final static String server_prefix = "/bridge";
    private final static int server_port = 8000;
    private ConcurrencyHandler handler;

    /**
     * Boots the server.
     *
     * The port and URL it listens on are given by server_prefix and
     * server_port.
     */
    public static void main(String[] args) throws IOException {
        final InetSocketAddress addr;
        final HttpServer server;

        addr = new InetSocketAddress("", server_port);

        server = HttpServer.create(addr, 10);
        server.createContext(server_prefix, 
                new ConcurrencyServer(new ConcurrencyHandler()));
        server.start();
    }

    /**
     * Create a server.
     *
     * @param h the handler to forward valid requests to.
     */
    public ConcurrencyServer(ConcurrencyHandler h)
    {
        this.handler = h;
    }

    /**
     * Handle a request from a client.
     *
     * Clients should initially request server_prefix, but when they have been
     * given an id, they should request server_prefix/id.
     *
     * @param request 
     */
    public void handle(HttpExchange request) {

        String input = this.getBody(request);
        Client c = this.getClient(request);

        this.handler.on_recieve(c, input);

        this.sendOutput(request, c.getResponse());

        //Touch the current client before seeing if any are timed out.
        c.touch();

        //Clients can be removed from the list during iteration
        Client.forEachClient(new ClientAction () {
            public void action(Client cl) {
                if (cl.timedOut())
                    handler.on_timeout(cl);
            }
        });

    }

    /**
     * Find the existing <code>Client</code> or create a new one.
     *
     * This looks at the URL, if it is equal to server_prefix, we have a new 
     * client. If it has an affix, then that should be the client's ID. If the 
     * client doesn't exist (it has timed out) then we treat it as a new one.
     *
     * @param request  The current request
     */
    private Client getClient(HttpExchange request)
    {
        String url = request.getRequestURI().getPath();

        //The HttpServer should cause this assertion to be always true.
        assert(url.substring(0, server_prefix.length()) == server_prefix);

        url = url.substring(server_prefix.length());

        if (url.length() > 0 && url.charAt(0) == '/')
        {
            String cliid = url.substring(1);
            if (Client.containsKey(cliid))
            {
                return Client.get(cliid);
            }
        }
        return Client.addnew();
    }

    /**
     * Extract the client's POST request. 
     *
     * If it is not possible, treat it as an empty request. This allows the
     * client to poll the server with an empty request when it has nothing to
     * say.
     *
     * @param request
     * @return The string
     */
    private String getBody(HttpExchange request)
    {
        InputStreamReader r = new InputStreamReader(request.getRequestBody());
        StringBuilder b = new StringBuilder();
        char[] tmp = new char[1000];
        
        try
        {
            while (tmp.length == r.read(tmp))
            {
                b.append(tmp);
            }
            b.append(tmp);
            r.close();
        }
        catch(IOException e)
        {
            System.out.println("Could not read from client:" + e);
            return "";
        }

        return b.toString();
    }

    /**
     * Send a string to the client.
     *
     * Silently handle and log any exception, there's not much we can do about
     * them.
     *
     * @param request
     * @param string   To be sent.
     */
    private void sendOutput(HttpExchange request, String response)
    {
        byte[] out = response.getBytes();
        // FIXME by DELETEing me
        // if (out.equals("[]")) 
        // {
        //     String fluff = "<html><head><title>Hi</title>\
        //     <script type=\"text/javascript\"\
        //      src=\"http://localhost/0/gp/svn/post_ajax.js\">\
        //      </script></head><body><h1>Hey</h1><pre>";
        //     out = fluff.concat(out);
        //     fluff = "</pre><form method=\"post\" action=\"\">\
        //     <textarea name=\"svg\" ></textarea>\
        //     <input type=\"submit\" onclick=\"ajax(); return false\"/>\
        //     </form></body></html>";
        //     out = out.concat(fluff);
        // }

        try
        {
            request.sendResponseHeaders(HTTP_OK, out.length);
            OutputStream s = request.getResponseBody();
            s.write(out);
            s.close();
            request.close();
        }
        catch(IOException e)
        {
            System.out.println("Could not write output to client: " + e + out);
        }
    }
}

